package cDB

import (
	"errors"

	"github.com/gin-gonic/gin"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"gitee.com/csingo/cLog"
)

type Mongo[T ModelInterface] struct{}

func (i Mongo[T]) Create(ctx *gin.Context, data T) (err error) {
	ptr := new(T)
	_, err = WithContextCollection(ctx, *ptr).InsertOne(ctx, data)
	return
}

func (i Mongo[T]) Insert(ctx *gin.Context, data []T) (err error) {
	ptr := new(T)

	var items []any
	for _, item := range data {
		items = append(items, item)
	}

	_, err = WithContextCollection(ctx, *ptr).InsertMany(ctx, items)
	return
}

func (i Mongo[T]) FirstOrCreate(ctx *gin.Context, data T, filter bson.M) (err error) {
	ptr := new(T)

	opts := options.FindOneAndUpdate().
		SetUpsert(true).
		SetReturnDocument(options.After)

	update := bson.M{
		"$setOnInsert": data,
	}

	result := WithContextCollection(ctx, *ptr).FindOneAndUpdate(ctx, filter, update, opts)
	err = result.Err()
	if err != nil && errors.Is(err, mongo.ErrNoDocuments) {
		return
	}

	if err = result.Decode(data); err != nil {
		return
	}

	return
}

func (i Mongo[T]) UpdateOrCreate(ctx *gin.Context, data T, fields []string) (err error) {
	ptr := new(T)
	opts := options.Update().SetUpsert(true)
	update := bson.M{
		"$set": data,
	}

	bsonBytes, err := bson.Marshal(data)
	if err != nil {
		return
	}

	var bsonM bson.M
	if err = bson.Unmarshal(bsonBytes, &bsonM); err != nil {
		return
	}

	var filter = make(bson.M)
	for _, field := range fields {
		filter[field] = bsonM[field]
	}

	_, err = WithContextCollection(ctx, *ptr).UpdateOne(ctx, filter, update, opts)
	return
}

func (i Mongo[T]) CreateOrIgnore(ctx *gin.Context, data T) (err error) {
	ptr := new(T)
	_, err = WithContextCollection(ctx, *ptr).InsertOne(ctx, data)

	if mongo.IsDuplicateKeyError(err) {
		return nil
	}

	return
}

func (i Mongo[T]) FindByConditions(ctx *gin.Context, filter bson.M) (result T, err error) {
	if len(filter) == 0 {
		return
	}
	ptr := new(T)
	data := WithContextCollection(ctx, *ptr).FindOne(ctx, filter)
	if err = data.Err(); err != nil {
		return
	}

	resultPtr := new(T)
	if err = data.Decode(resultPtr); err != nil {
		return
	}

	result = *resultPtr

	return
}

func (i Mongo[T]) GetByConditions(ctx *gin.Context, filter bson.M) (result []T, err error) {
	result = make([]T, 0)
	ptr := new(T)
	cursor, err := WithContextCollection(ctx, *ptr).Find(ctx, filter)
	if err != nil {
		return
	}
	defer cursor.Close(ctx)

	if err = cursor.All(ctx, &result); err != nil {
		return
	}

	return
}

func (i Mongo[T]) CountByConditions(ctx *gin.Context, filter bson.M) (total int64, err error) {
	ptr := new(T)
	total, err = WithContextCollection(ctx, *ptr).CountDocuments(ctx, filter)
	return
}

func (i Mongo[T]) ListByConditions(ctx *gin.Context, filter bson.M, page, size int64, order bson.D) (result []T, err error) {
	ptr := new(T)
	opts := options.Find()
	if len(order) > 0 {
		opts = opts.SetSort(order)
	}
	if page < 1 {
		page = 1
	}
	if size > 0 {
		skip := (page - 1) * size
		opts = opts.SetSkip(skip).SetLimit(size)
	}

	cursor, err := WithContextCollection(ctx, *ptr).Find(ctx, filter, opts)
	if err != nil {
		return
	}
	defer cursor.Close(ctx)

	result = make([]T, 0)
	if err = cursor.All(ctx, &result); err != nil {
		return
	}

	return
}

func (i Mongo[T]) UpdateByConditions(ctx *gin.Context, filter bson.M, data bson.M) (err error) {
	ptr := new(T)

	update := bson.M{
		"$set": data,
	}

	result, err := WithContextCollection(ctx, *ptr).UpdateMany(ctx, filter, update)
	cLog.WithContext(ctx, map[string]any{"result": result}).Debug("更新记录")

	return
}

func (i Mongo[T]) DeleteByConditions(ctx *gin.Context, filter bson.M) (err error) {
	ptr := new(T)
	result, err := WithContextCollection(ctx, *ptr).DeleteMany(ctx, filter)
	cLog.WithContext(ctx, map[string]any{"result": result}).Debug("删除记录")

	return
}
